﻿using System;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using xn;

namespace Nui
{
    /// <summary>
    /// Represents a Natural User Interface template context.
    /// </summary>
    public class NuiHandTracker
    {
        #region Constants

        /// <summary>
        /// Default configuration file path.
        /// </summary>
        protected const string CONFIGURATION = @"SamplesConfig.xml";

        /// <summary>
        /// Horizontal bitmap dpi.
        /// </summary>
        readonly int DPI_X = 96;

        /// <summary>
        /// Vertical bitmap dpi.
        /// </summary>
        readonly int DPI_Y = 96;

        #endregion

        #region Members

        /// <summary>
        /// Thread responsible for image and depth camera updates.
        /// </summary>
        protected Thread _cameraThread;

        /// <summary>
        /// Indicates whether the thread is running.
        /// </summary>
        protected bool _isRunning = true;

        /// <summary>
        /// Raw image metadata.
        /// </summary>
        protected ImageMetaData _imageMD;

        /// <summary>
        /// OpenNI image generator.
        /// </summary>
        protected ImageGenerator _imageGenerator;

        /// <summary>
        /// Raw image source.
        /// </summary>
        protected WriteableBitmap _cameraImage;

        /// <summary>
        /// OpenNI depth generator.
        /// </summary>
        protected DepthGenerator _depthGenerator;

        /// <summary>
        /// OpenNI gesture generator.
        /// </summary>
        GestureGenerator _gestureGenerator;

        /// <summary>
        /// OPenNI hands generator.
        /// </summary>
        HandsGenerator _handsGenerator;

        /// <summary>
        /// OpenNI main Context.
        /// </summary>
        protected Context _context;

        #endregion

        #region Push implementation members

        /// <summary>
        /// Time indicating the interval between two pushes.
        /// </summary>
        System.Timers.Timer _timer = new System.Timers.Timer(300);

        /// <summary>
        /// Push distance in the Z-Axis.
        /// </summary>
        float _pushThreshold = 100;

        /// <summary>
        /// Hand current horizontal position.
        /// </summary>
        float _currentX;

        /// <summary>
        /// Hand current vertical position.
        /// </summary>
        float _currentY;

        /// <summary>
        /// Hand current depth position.
        /// </summary>
        float _currentZ;

        /// <summary>
        /// Hand previous depth position.
        /// </summary>
        float _previousZ;

        /// <summary>
        /// Indicates whether the hand is moving forward or backwards.
        /// </summary>
        bool _isFirstPush;

        #endregion

        #region Properties

        /// <summary>
        /// Image width.
        /// </summary>
        public int Width { get; protected set; }

        /// <summary>
        /// Image height.
        /// </summary>
        public int Height { get; protected set; }

        /// <summary>
        /// Camera raw image source.
        /// </summary>
        public ImageSource CameraImage
        {
            get
            {
                _cameraImage.Lock();
                _cameraImage.WritePixels(new Int32Rect(0, 0, _imageMD.XRes, _imageMD.YRes), _imageMD.ImageMapPtr, (int)_imageMD.DataSize, _cameraImage.BackBufferStride);
                _cameraImage.Unlock();

                return _cameraImage;
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Hover event handler.
        /// </summary>
        /// <param name="sender">The object firing the event.</param>
        /// <param name="e">Gesture coordinates.</param>
        public delegate void HoverHandler(object sender, NuiPositionEventArgs e);

        /// <summary>
        /// Hover event.
        /// </summary>
        public event HoverHandler Hover;

        /// <summary>
        /// Push event handler.
        /// </summary>
        /// <param name="sender">The object firing the event.</param>
        /// <param name="e">Gesture coordinates.</param>
        public delegate void PushHandler(object sender, NuiPositionEventArgs e);

        /// <summary>
        /// Push event.
        /// </summary>
        public event PushHandler Push;

        /// <summary>
        /// Test event handler.
        /// </summary>
        /// <param name="sender">The object firing the event.</param>
        /// <param name="e">Gesture coordinates.</param>
        public delegate void TestHandler(object sender, NuiPositionEventArgs e);

        /// <summary>
        /// Test event.
        /// </summary>
        public event TestHandler Test;


        #endregion

        #region Constructors

        /// <summary>
        /// Creates a new instance of NuiSensor with the default configuration file.
        /// </summary>
        public NuiHandTracker()
            : this(CONFIGURATION)
        {
        }

        /// <summary>
        /// Creates a new instance of NuiSensor with the specified configuration file.
        /// </summary>
        /// <param name="configuration">Configuration file path.</param>
        public NuiHandTracker(string configuration)
        {
            try
            {
                _context = new Context(configuration);
            }
            catch
            {
                throw new Exception("Configuration file not found.");
            }

            _imageMD = new ImageMetaData();
            _imageGenerator = _context.FindExistingNode(NodeType.Image) as ImageGenerator;
            _depthGenerator = _context.FindExistingNode(NodeType.Depth) as DepthGenerator;

            MapOutputMode mapMode = _depthGenerator.GetMapOutputMode();

            Width = (int)mapMode.nXRes;
            Height = (int)mapMode.nYRes;

            _cameraImage = new WriteableBitmap(Width, Height, DPI_X, DPI_Y, PixelFormats.Rgb24, null);

            _timer.Elapsed += new System.Timers.ElapsedEventHandler(Timer_Elapsed);
            _timer.Start();

            _gestureGenerator = new GestureGenerator(_context);
            _gestureGenerator.AddGesture("Wave");
            //_gestureGenerator.AddGesture("Click");
            _gestureGenerator.GestureRecognized += new GestureGenerator.GestureRecognizedHandler(GestureGenerator_GestureRecognized);

            _handsGenerator = new HandsGenerator(_context);
            _handsGenerator.HandUpdate += new HandsGenerator.HandUpdateHandler(HandsGenerator_HandUpdate);
            _handsGenerator.HandDestroy += new HandsGenerator.HandDestroyHandler(HandsGenerator_HandDestroy);

            _gestureGenerator.StartGenerating();
            _handsGenerator.StartGenerating();

            _isRunning = true;
            _cameraThread = new Thread(CameraThread);
            _cameraThread.IsBackground = true;
            _cameraThread.Start();
        }

        #endregion

        #region Private methods

        /// <summary>
        /// Updates image and depth values.
        /// </summary>
        private unsafe void CameraThread()
        {
            while (_isRunning)
            {
                try
                {
                    _context.WaitOneUpdateAll(_depthGenerator);
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine(string.Format("Error updating context on {0}, {1}", DateTime.Now.ToShortDateString(), DateTime.Now.ToShortTimeString()));
                }

                _imageGenerator.GetMetaData(_imageMD);
            }
        }

        #endregion

        #region Event handlers

        void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (Math.Abs(_currentZ - _previousZ) > _pushThreshold)
            {
                if (_isFirstPush) // ---> The hand moved forward.
                {
                    if (Push != null)
                    {
                        Push(this, new NuiPositionEventArgs(_currentX, _currentY, _currentZ));
                    }

                    _isFirstPush = false;
                }
                else // ---> The hand pushed and now returns backwards.
                {
                    _isFirstPush = true;
                }
            }

            _previousZ = _currentZ;
        }

        void GestureGenerator_GestureRecognized(ProductionNode node, string strGesture, ref Point3D idPosition, ref Point3D endPosition)
        {
            _handsGenerator.StartTracking(ref endPosition);
            _gestureGenerator.RemoveGesture(strGesture);

            Test(this, new NuiPositionEventArgs(_currentX, _currentY, _currentZ));
        }

        void HandsGenerator_HandUpdate(ProductionNode node, uint id, ref Point3D position, float fTime)
        {
            _currentX = position.X;
            _currentY = position.Y;
            _currentZ = position.Z;

            Hover(this, new NuiPositionEventArgs(_currentX, _currentY, _currentZ));
        }

        void HandsGenerator_HandDestroy(ProductionNode node, uint id, float fTime)
        {
            _gestureGenerator.AddGesture("Wave");
        }

        #endregion
    }
}
